/* init.c */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/resource.h>

#include "log.h"

#define COMMENT         '#'
#define NOFILE          1024
#define MAXPATH         1024
#define TINYWEB         "tinyweb"
#define CONFFILE        "tiny.conf"
#define LOCKFILE        "/var/run/tinyweb.pid"
#define LOCKMODE        (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)

int     port;               /* connection port */
int     conn_max;           /* maximum connection number */
int     child_max;          /* maximum child number */
int     child_min;          /* minimum child number */
int     child_init;         /* initial child number */
int     child_interval;     /* interval for check child state */
char    webdir[MAXPATH];    /* path of the tiny directory */
char    cgidir[MAXPATH];    /* path of the cgi directory */
char    logfile[MAXPATH];   /* path of the log file */

/* read configure file */
void readconf()
{
    FILE *fp;
    char line[MAXPATH];
    
    if ((fp = fopen(CONFFILE, "r")) == NULL) {
        perror("open CONFFILE error");
        exit(1);
    }

    /* get the configure */
    while (fgets(line, sizeof(line), fp) != NULL) {
        if (line[0] == COMMENT)
            continue;
        
        else if (strncmp(line, "port=", 5) == 0) {
            port = strtol(line+5, NULL, 0);
            if (port <= 0) {
                fprintf(stderr, "unexpected port in %s\n", CONFFILE);
                exit(1);
            }
        }

        else if (strncmp(line, "conn_max=", 9) == 0) {
            conn_max = strtol(line+9, NULL, 0);
            if (conn_max <= 0) {
                fprintf(stderr, "unexpected conn_max in %s\n", CONFFILE);
                exit(1);
            }
        }

        else if (strncmp(line, "child_max=", 10) == 0) {
            child_max = strtol(line+10, NULL, 0);
            if (child_max <= 0) {
                fprintf(stderr, "unexpected child_max in %s\n", CONFFILE);
                exit(1);
            }
        }

        else if (strncmp(line, "child_min=", 10) == 0) {
            child_min = strtol(line+10, NULL, 0);
            if (child_min <= 0) {
                fprintf(stderr, "unexpected child_min in %s\n", CONFFILE);
                exit(1);
            }
        }

        else if (strncmp(line, "child_init=", 11) == 0) {
            child_init = strtol(line+11, NULL, 0);
            if (child_init <= 0) {
                fprintf(stderr, "unexpected conn_init in %s\n", CONFFILE);
                exit(1);
            }
        }

        else if (strncmp(line, "child_interval=", 15) == 0) {
            child_interval = strtol(line+15, NULL, 0);
            if (child_interval <= 0) {
                fprintf(stderr, "unexpected conn_interval in %s\n",
                    CONFFILE);
                exit(1);
            }
        }

        else if (strncmp(line, "webdir=", 7) == 0) {
            strcpy(webdir, line+7);
            webdir[strlen(webdir)-1] = '\0';
            if (webdir[0] == '\0') {
                fprintf(stderr, "unexpected webdir in %s\n", CONFFILE);
                exit(1);
            }
        }

        else if (strncmp(line, "cgidir=", 7) == 0) {
            strcpy(cgidir, line+7);
            cgidir[strlen(cgidir)-1] = '\0';
            if (cgidir[0] == '\0') {
                fprintf(stderr, "unexpected cgidir in %s\n", CONFFILE);
                exit(1);
            }
        }

        else if (strncmp(line, "logfile=", 8) == 0) {
            strcpy(logfile, webdir);
            strcat(logfile, "/");
            strcat(logfile, line+8);
            logfile[strlen(logfile)-1] = '\0';
            if (logfile[0] == '\0') {
                fprintf(stderr, "unexpected logfile in %s\n", CONFFILE);
                exit(1);
            }
        }
    }
}

int get_rlmax()
{
    struct rlimit rl;

    /* get maximun number of file descriptors */
    if (getrlimit(RLIMIT_NOFILE, &rl) != 0) {
        log_error("getrlimit RLIMIT_NOFILE error: %s", strerror(errno)); 
        exit(1);
    }

    if (rl.rlim_max == RLIM_INFINITY)
        return NOFILE;
    else
        return rl.rlim_max;
}

void daemonize(int listenfd)
{
    int                 i, fd0, fd1, fd2, rlmax;
    pid_t               pid;
    struct sigaction    action;

    /* clear file creation mask */
    umask(0);

    /* become a session leader to lose controlling TTY */
    if ((pid = fork()) < 0) {
        perror("fork error");
        exit(1);
    }
    else if (pid > 0)
        exit(0);
    else 
        setsid();

    /* ensure future opens won't allocate controlling TTYs */
    action.sa_handler = SIG_IGN;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    if (sigaction(SIGHUP, &action, NULL) < 0) {
        perror("sigaction error");
        exit(1);
    }
    if ((pid = fork()) < 0) {
        perror("fork error");
        exit(1);
    }
    else if (pid > 0)
        exit(0);

    /* change the current working directory to the root */
    if (chdir("/") < 0) {
        perror("chdir error");
        exit(1);
    }

    log_open(TINYWEB);

    /* close all file descriptors except listenfd */
    rlmax = get_rlmax();
    for (i = 0; i < rlmax; i++) {
        if (i != listenfd)
            close(i);
    }

    /* attach file descriptors 0, 1 to /dev/null, 2 to logfile */
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = open(logfile, O_WRONLY|O_TRUNC);
    if (fd2 < 0)
        log_error("open logfile %s error: %s", logfile, strerror(errno));
    if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
        log_error("unexpected file descriptors %d %d %d", 
            fd0, fd1, fd2);
        exit(1);
    }
}

/* check if this program is already running */
int already_running()
{
    int             fd;
    char            buf[16];
    struct flock    fl;

    if ((fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE)) < 0) {
        log_error("can't open %s: %s", LOCKFILE, strerror(errno)); 
        exit(1); 
    }

    fl.l_type = F_WRLCK;
    fl.l_start = 0;
    fl.l_whence = SEEK_SET;
    fl.l_len = 0;
    if (fcntl(fd, F_SETLK, &fl) < 0) {
        if (errno == EACCES || errno == EAGAIN) {
            close(fd);
            return 1;
        }
        log_error("can't lock %s: %s", LOCKFILE, strerror(errno));
        exit(1);
    }

    if (ftruncate(fd, 0) < 0) {
        log_error("ftruncate %s error: %s", LOCKFILE, strerror(errno));
        exit(1);
    }

    sprintf(buf, "%ld", (long)getpid());
    if (write(fd, buf, strlen(buf)+1) < 0) {
        log_error("write %s error: %s", LOCKFILE, strerror(errno));
        exit(1);
    }

    return 0;
}
